package org.loong.crypto.model.asn1.sm2;

import java.math.BigInteger;
import java.util.Enumeration;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.BERTags;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.util.BigIntegers;

/**
 * SM2 privateKey.
 * 
 * <pre>
 * SM2PrivateKey ::= SEQUENCE {
 *     version INTEGER { sm2PrivkeyVer1(1) } (sm2PrivkeyVer1),
 *     privateKey OCTET STRING,
 *     parameters [0] Parameters OPTIONAL,
 *     publicKey [1] BIT STRING OPTIONAL 
 * }
 * </pre>
 */
public class SM2PrivateKey extends ASN1Object {

    private ASN1Sequence seq;

    private SM2PrivateKey(ASN1Sequence seq) {
        this.seq = seq;
    }

    public static SM2PrivateKey getInstance(Object obj) {
        if (obj instanceof SM2PrivateKey) {
            return (SM2PrivateKey) obj;
        }

        if (obj != null) {
            return new SM2PrivateKey(ASN1Sequence.getInstance(obj));
        }

        return null;
    }

    public SM2PrivateKey(int orderBitLength, BigInteger key) {
        byte[] bytes = BigIntegers.asUnsignedByteArray((orderBitLength + 7) / 8, key);
       
        ASN1EncodableVector v = new ASN1EncodableVector(2);
        v.add(new ASN1Integer(1));
        v.add(new DEROctetString(bytes));
        this.seq = new DERSequence(v);
    }

    public SM2PrivateKey(int orderBitLength, BigInteger key, ASN1Encodable parameters) {
        this(orderBitLength, key, null, parameters);
    }

    public SM2PrivateKey(int orderBitLength, BigInteger key, DERBitString publicKey, ASN1Encodable parameters) {
        byte[] bytes = BigIntegers.asUnsignedByteArray((orderBitLength + 7) / 8, key);

        ASN1EncodableVector v = new ASN1EncodableVector(4);
        v.add(new ASN1Integer(1));
        v.add(new DEROctetString(bytes));

        if (parameters != null) {
            v.add(new DERTaggedObject(true, 0, parameters));
        }

        if (publicKey != null) {
            v.add(new DERTaggedObject(true, 1, publicKey));
        }

        this.seq = new DERSequence(v);
    }

    public BigInteger getPrivateKey() {
        ASN1OctetString octs = (ASN1OctetString) seq.getObjectAt(1);
        return new BigInteger(1, octs.getOctets());
    }

    public DERBitString getPublicKey() {
        return (DERBitString) getObjectInTag(1, BERTags.BIT_STRING);
    }

    public ASN1Object getParametersObject() {
        return getObjectInTag(0, -1);
    }

    private ASN1Primitive getObjectInTag(int tagNo, int baseTagNo) {
        Enumeration<?> e = seq.getObjects();
        while (e.hasMoreElements()) {
            ASN1Encodable obj = (ASN1Encodable) e.nextElement();
            if (obj instanceof ASN1TaggedObject) {
                ASN1TaggedObject tag = (ASN1TaggedObject) obj;
                if (tag.hasContextTag(tagNo)) {
                    return baseTagNo < 0 ? tag.getExplicitBaseObject().toASN1Primitive() : tag.getBaseUniversal(true, baseTagNo);
                }
            }
        }
        return null;
    }

    @Override
    public ASN1Primitive toASN1Primitive() {
        return seq;
    }
}